﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint.Taxonomy;
using Microsoft.SharePoint;
using AgileShare.Framework.Core;

namespace AgileShare.Framework.Metadata.Helpers
{
	/// <summary>
	/// This class contains helper methods for working with SharePoint Managed Metadata
	/// - All methods should be static
	/// - DO NOT USE SPContext
	/// </summary>
	public class TermStoreHelper
	{
		/// <summary>
		/// Gets the term store.
		/// </summary>
		/// <param name="termStoreName">Name of the term store.</param>
		/// <param name="session">The session.</param>
		/// <returns></returns>
		public static TermStore GetTermStore(string termStoreName, TaxonomySession session)
		{
			TermStore termStore = null;
			try
			{
				termStore = session.TermStores[termStoreName];
			}
			catch (Exception ex)
			{
				string errorMessage = string.Format("Term Store not found with Term Store Name '{0}'.", termStoreName);
				TraceProvider.Log(errorMessage, TraceProviderCategories.Default, (uint)TraceProviderEventIds.TermStoreHelper, TraceProviderSeverity.Information, ex);
			}
			return termStore;
		}

		/// <summary>
		/// Gets the term group.
		/// </summary>
		/// <param name="groupName">Name of the group.</param>
		/// <param name="termStoreName">Name of the term store.</param>
		/// <param name="session">The session.</param>
		/// <returns></returns>
		public static Group GetGroup(string groupName, string termStoreName, TaxonomySession session)
		{
			Group group = null;
			TermStore termStore = GetTermStore(termStoreName, session);
			if (termStore != null)
			{
				try
				{
					group = termStore.Groups[groupName];
				}
				catch (Exception ex)
				{
					string errorMessage = string.Format("Term Store Group not found at Term Store '{0}' with Group Name '{1}'.", termStoreName, groupName);
					TraceProvider.Log(errorMessage, TraceProviderCategories.Default, (uint)TraceProviderEventIds.TermStoreHelper, TraceProviderSeverity.Information, ex);
				}
			}
			return group;
		}

		/// <summary>
		/// Gets a Term Set from the Term Store.
		/// </summary>
		/// <param name="termSetName">The name of the term set.</param>
		/// <param name="groupName">The term store group name.</param>
		/// <param name="termStoreName">The term store name.</param>
		/// <param name="session">The TaxonomySession to use.</param>
		/// <returns>
		/// A TermSet.
		/// </returns>
		public static TermSet GetTermSet(string termSetName, string groupName, string termStoreName, TaxonomySession session)
		{
			TermSet termSet = null;
			Group group = GetGroup(groupName, termStoreName, session);
			if (group != null)
			{
				try
				{
					termSet = group.TermSets[termSetName];
				}
				catch (Exception ex)
				{
					string errorMessage = string.Format("Term Set not found at Term Store '{0}', Group '{1}', Term Set Name {2}.", termStoreName, groupName, termSetName);
					TraceProvider.Log(errorMessage, TraceProviderCategories.Default, (uint)TraceProviderEventIds.TermStoreHelper, TraceProviderSeverity.Information, ex);
				}
			}
			return termSet;
		}

		/// <summary>
		/// Gets a Term Set from the Term Store.
		/// </summary>
		/// <param name="termSetId">The term set id.</param>
		/// <param name="groupName">The term store group name.</param>
		/// <param name="termStoreName">The term store name.</param>
		/// <param name="session">The TaxonomySession to use.</param>
		/// <returns>
		/// A TermSet.
		/// </returns>
		public static TermSet GetTermSet(Guid termSetId, string groupName, string termStoreName, TaxonomySession session)
		{
			TermSet termSet = null;
			Group group = GetGroup(groupName, termStoreName, session);
			if (group != null)
			{
				try
				{
					termSet = group.TermSets[termSetId];
				}
				catch (Exception ex)
				{
					string errorMessage = string.Format("Term Set not found at Term Store '{0}', Group '{1}', Term Set ID {2}.", termStoreName, groupName, termSetId); 
					TraceProvider.Log(errorMessage, TraceProviderCategories.Default, (uint)TraceProviderEventIds.TermStoreHelper, TraceProviderSeverity.Information, ex);
				}
			}
			return termSet;
		}

		/// <summary>
		/// Gets a Term from the Term Store.
		/// </summary>
		/// <param name="termName">The name of the term.</param>
		/// <param name="termSetName">The name of the term set.</param>
		/// <param name="groupName">The term store group name.</param>
		/// <param name="termStoreName">The term store name.</param>
		/// <param name="session">The session.</param>
		/// <param name="caseSensitive">true does a case sensitive comparison</param>
		/// <returns>
		/// A Term.
		/// </returns>
		public static Term GetTerm(string termName, string termSetName, string groupName, string termStoreName, TaxonomySession session, bool caseSensitive = true)
		{
			Term term = null;
			TermSet termSet = GetTermSet(termSetName, groupName, termStoreName, session);
			if (termSet != null)
			{
				if (caseSensitive)
				{
					term = termSet.Terms.SingleOrDefault(x => x.Name == termName);
				}
				else
				{
					term = termSet.Terms.SingleOrDefault(x => x.Name.ToLower() == termName.ToLower());
				}

				if (term == null)
				{
					// TODO: Research what is this for
					Term auxiliarTerm = termSet.Terms.FirstOrDefault(x => x.Terms.Any(y => y.Name == termName));
					if (auxiliarTerm != null)
					{
						term = termSet.Terms[auxiliarTerm.Name].Terms[termName];
					}
				}
			}
			return term;
		}

		/// <summary>
		/// Returns a TaxonomyFieldValue for a Term.
		/// </summary>
		/// <param name="termName">The name of the term.</param>
		/// <param name="termSetName">The name of the term set.</param>
		/// <param name="groupName">The term store group name.</param>
		/// <param name="termStoreName">The term store name.</param>
		/// <param name="taxonomyField">The TaxonomyField to use.</param>
		/// <param name="taxonomySession">The TaxonomySession to use.</param>
		/// <returns></returns>
		public static TaxonomyFieldValue GetTaxonomyFieldValue(string termName, string termSetName, string groupName, string termStoreName, TaxonomyField taxonomyField, TaxonomySession taxonomySession)
		{
			TaxonomyFieldValue taxonomyFieldValue = new TaxonomyFieldValue(taxonomyField);
			Term term = GetTerm(termName, termSetName, groupName, termStoreName, taxonomySession);
			if (term != null)
			{
				taxonomyFieldValue.TermGuid = term.Id.ToString();
			}
			return taxonomyFieldValue;
		}

		/// <summary>
		/// Sets the value to an item field.
		/// </summary>
		/// <param name="item">The item to use.</param>
		/// <param name="fieldId">The Guid of the field to use.</param>
		/// <param name="valueTermGuid">The Id of the Term to use/</param>
		/// <param name="taxonomySession">The TaxonomySession to use.</param>
		public static void SetValue(SPListItem item, Guid fieldId, string valueTermGuid, TaxonomySession taxonomySession)
		{
			TaxonomyField taxonomyField = (TaxonomyField)item.Fields[fieldId];
			Term term = taxonomySession.GetTerm(new Guid(valueTermGuid));
			taxonomyField.SetFieldValue(item, term);
		}

		/// <summary>
		/// Sets the value to the first Term match in the TermSet collection for the specified field.
		/// </summary>
		/// <param name="termName">The term name to use.</param>
		/// <param name="fieldId">The Guid of the Field to use.</param>
		/// <param name="listItem">The list item to use.</param>
		/// <param name="taxonomySession">The TaxonomySession to use.</param>
		public static void SetFirstMatchValue(string termName, Guid fieldId, SPListItem listItem, TaxonomySession taxonomySession)
		{
			SPWeb web = listItem.Web;

			web.AllowUnsafeUpdates = true;
			TaxonomyField field = (TaxonomyField)listItem.Fields[fieldId];
			TermStore termStore = taxonomySession.TermStores[field.SspId];
			TermSet termSet = termStore.GetTermSet(field.TermSetId);
			Term term = termSet.GetTerms(termName, true).FirstOrDefault();
			if (term != null)
			{
				field.SetFieldValue(listItem, term);
				listItem.Update();
			}
		}

		/// <summary>
		/// Ensures the group.
		/// </summary>
		/// <param name="groupName">Name of the group.</param>
		/// <param name="termStoreName">Name of the term store.</param>
		/// <param name="session">The session.</param>
		/// <returns></returns>
		public static Group EnsureGroup(string groupName, string termStoreName, TaxonomySession session)
		{			
			Group group = GetGroup(groupName, termStoreName, session);
			if (group == null)
			{
				TermStore termStore = GetTermStore(termStoreName, session);
				group = termStore.CreateGroup(groupName);
				termStore.CommitAll();				
			}
			return group;			
		}

		/// <summary>
		/// Ensures the term set.
		/// </summary>
		/// <param name="termSetName">Name of the term set.</param>
		/// <param name="termSetId">The term set id.</param>
		/// <param name="groupName">Name of the group.</param>
		/// <param name="termStoreName">Name of the term store.</param>
		/// <param name="session">The session.</param>
		/// <returns></returns>
		public static TermSet EnsureTermSet(string termSetName, Guid termSetId, string groupName, string termStoreName, TaxonomySession session)
		{
			Group group = EnsureGroup(groupName, termStoreName, session);
			TermSet termSet = GetTermSet(termSetId, groupName, termStoreName, session) ?? GetTermSet(termSetName, groupName, termStoreName, session);			
			if (termSet == null)
			{
				TermStore termStore = GetTermStore(termStoreName, session);
				termSet = group.CreateTermSet(termSetName, termSetId);
				termStore.CommitAll();
			}
			return termSet;			
		}

		/// <summary>
		/// Ensures the term set.
		/// </summary>
		/// <param name="termSetName">Name of the term set.</param>
		/// <param name="termSetId">The term set id.</param>
		/// <param name="groupName">Name of the group.</param>
		/// <param name="termStoreName">Name of the term store.</param>
		/// <param name="lcid">The lcid.</param>
		/// <param name="session">The session.</param>
		/// <param name="terms">The terms.</param>
		/// <returns></returns>
		public static TermSet EnsureTermSet(string termSetName, Guid termSetId, string groupName, string termStoreName, int lcid, TaxonomySession session, params string[] terms)
		{
			Group group = EnsureGroup(groupName, termStoreName, session);
			TermSet termSet = GetTermSet(termSetId, groupName, termStoreName, session);
			if (termSet == null)
			{
				TermStore termStore = GetTermStore(termStoreName, session);
				termSet = group.CreateTermSet(termSetName, termSetId, lcid);
				foreach (string term in terms)
				{
					termSet.CreateTerm(term, lcid);
				}
				termStore.CommitAll();
			}
			return termSet;
		}

		/// <summary>
		/// Ensures the term set with two levels.
		/// </summary>
		/// <param name="termSetName">Name of the term set.</param>
		/// <param name="termSetId">The term set id.</param>
		/// <param name="groupName">Name of the group.</param>
		/// <param name="termStoreName">Name of the term store.</param>
		/// <param name="lcid">The lcid.</param>
		/// <param name="session">The session.</param>
		/// <param name="terms">The terms.</param>
		/// <returns></returns>
		public static TermSet EnsureTermSet(string termSetName, Guid termSetId, string groupName, string termStoreName, int lcid, TaxonomySession session, List<KeyValuePair<string, string[]>> terms)
		{
			Group group = EnsureGroup(groupName, termStoreName, session);
			TermSet termSet = GetTermSet(termSetId, groupName, termStoreName, session);
			if (termSet == null)
			{
				TermStore termStore = GetTermStore(termStoreName, session);
				termSet = group.CreateTermSet(termSetName, termSetId, lcid);
				foreach (var term in terms)
				{
					Term firstLevelTerm = termSet.CreateTerm(term.Key, lcid);

					foreach (var secondLevelterms in term.Value)
					{
						firstLevelTerm.CreateTerm(secondLevelterms, lcid);
					}
				}
				termStore.CommitAll();
			}
			return termSet;
		}

		/// <summary>
		/// Links the taxonomy field.
		/// </summary>
		/// <param name="fieldId">The field id.</param>
		/// <param name="termSetName">Name of the term set.</param>
		/// <param name="groupName">Name of the group.</param>
		/// <param name="termStoreName">Name of the term store.</param>
		/// <param name="session">The session.</param>
		/// <param name="site">The site.</param>
		/// <param name="pushChangesToLists">if set to <c>true</c> [push changes to lists].</param>
		public static void LinkTaxonomyField(Guid fieldId, string termSetName, string groupName, string termStoreName, TaxonomySession session, SPSite site, bool pushChangesToLists = true)
		{
			if (site.RootWeb.Fields.Contains(fieldId))
			{
				TermSet termSet = GetTermSet(termSetName, groupName, termStoreName, session);
				LinkTaxonomyField(fieldId, termSet, site, pushChangesToLists);
			}
		}

		/// <summary>
		/// Links the taxonomy field.
		/// </summary>
		/// <param name="fieldId">The field id.</param>
		/// <param name="termSetId">The term set id.</param>
		/// <param name="groupName">Name of the group.</param>
		/// <param name="termStoreName">Name of the term store.</param>
		/// <param name="session">The session.</param>
		/// <param name="site">The site.</param>
		/// <param name="pushChangesToLists">if set to <c>true</c> [push changes to lists].</param>
		public static void LinkTaxonomyField(Guid fieldId, Guid termSetId, string groupName, string termStoreName, TaxonomySession session, SPSite site, bool pushChangesToLists = true)
		{
			if (site.RootWeb.Fields.Contains(fieldId))
			{
				TermSet termSet = GetTermSet(termSetId, groupName, termStoreName, session);
				LinkTaxonomyField(fieldId, termSet, site, pushChangesToLists);
			}
		}

		/// <summary>
		/// Links the taxonomy field.
		/// </summary>
		/// <param name="fieldId">The field id.</param>
		/// <param name="termSet">The term set.</param>
		/// <param name="site">The site.</param>
		/// <param name="pushChangesToLists">if set to <c>true</c> [push changes to lists].</param>
		public static void LinkTaxonomyField(Guid fieldId, TermSet termSet, SPSite site, bool pushChangesToLists = true)
		{
			if (termSet != null)
			{
				TaxonomyField field = site.RootWeb.Fields[fieldId] as TaxonomyField;
				field.SspId = termSet.TermStore.Id;
				field.TermSetId = termSet.Id;
				field.TargetTemplate = string.Empty;
				field.AnchorId = Guid.Empty;
				field.Update(pushChangesToLists);
			}
		}
	}
}
